package com.region.common.utils;

import com.region.common.adapter.logging.LoggerAdapter;
import com.region.common.adapter.logging.LoggerAdapterFactory;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * reflect util function
 *
 * @author liujieyu
 * @time 2023/5/23 23:26
 * @description
 */
public class ReflectUtils {

    private static final LoggerAdapter logger = LoggerAdapterFactory.getLogger(ReflectUtils.class);

    /**
     * The transform class object from type
     *
     * @param type
     * @return
     */
    public static Class<?> transform(Type type) {
        Class<?> returnClass = null;
        if (type instanceof ParameterizedType) {
            returnClass = (Class<?>) ((ParameterizedType) type).getRawType();
        } else if (type instanceof Class) {
            returnClass = (Class<?>) type;
        }
        return returnClass;
    }

    /**
     * Gets the actual type in the generic
     * @param type
     * @return
     */
    public static Type[] getActuatorTypeArgs(Type type) {
        if (type instanceof ParameterizedType) {
            return ((ParameterizedTypeImpl) type).getActualTypeArguments();
        }
        return null;
    }

    /**
     * Assigns a value to a property of a specified object
     *
     * @param target Object
     * @param field  The object of field
     * @param value  setting attribute
     */
    public static void setFieldValue(Object target, Field field, Object value) {
        try {
            field.setAccessible(true);
            field.set(target, value);
        } catch (IllegalAccessException e) {
            logger.error("Cannot set the Field of Object [" + target + "]; set value [" + value + "]; error:" + e.getMessage());
        }
    }

    /**
     * Assigns a value to a property of a specified object
     *
     * @param target
     * @param fieldName
     * @param value
     */
    public static void setFieldValue(Object target, String fieldName, Object value) {
        setFieldValue(target, getField(target.getClass(), fieldName), value);
    }

    /**
     * Getting the value of a property in an object
     *
     * @param field
     * @param target
     * @return
     */
    public static Object getFieldValueByObject(Field field, Object target) {
        try {
            field.setAccessible(true);
            Object value = field.get(target);
            return value;
        } catch (IllegalAccessException e) {
            logger.error("Cannot get the Field [" + field.getName() + "] of Object [" + target + "]; error:" + e.getMessage(), e);
        }
        return null;
    }

    /**
     * Getting the value of a property in an object
     *
     * @param fieldName
     * @param target
     * @return
     */
    public static Object getFieldValueByObject(String fieldName, Object target) {
        try {
            return getFieldValueByObject(getField(target.getClass(), fieldName), target);
        } catch (Throwable e) {
            logger.error("Cannot get the Field [" + fieldName + "] of Object [" + target + "]; error:" + e.getMessage());
        }
        return null;
    }

    /**
     * Convert an object to key=value&key=value format
     * 1: Determine whether the requirements for arrays, collections, and objects to be convertible are met.
     * 2: for array and collection types
     * 3: for key-value types
     * 4: for base types
     * End up with regular types
     *
     * @param object    object to be parsed
     * @param recursion Is it recursive
     * @return map
     */
    public static Map<String, Object> getKeyValueByObject(Object object, boolean recursion) {
        Map<String, Object> result = new HashMap<>(16);
        if (isBasicGenericType(object.getClass())) {
            return Collections.EMPTY_MAP;
        }
        List<String> fieldNames = getFieldsName(object.getClass());
        // Iterate by each field
        for (String fieldName : fieldNames) {
            // Get the property Class object
            Class<?> fieldClass = getFieldClass(object.getClass(), fieldName);
            if (fieldClass == null || getFieldValueByObject(fieldName, object) == null) {
                continue;
            }
            Field field = getField(object.getClass(), fieldName);
            // Processing Fields
            if (isCollectOrMapOrArrayClass(fieldClass)) {
                // Collection type
                if (field == null) {
                    continue;
                }
                Map<String, Object> objectMap = handleCollectMapArray(field, object, fieldClass, getFieldValueByObject(field, object));
                for (Map.Entry<String, Object> entry : objectMap.entrySet()) {
                    result.put(fieldName + entry.getKey(), entry.getValue());
                }
            } else if (isBasicGenericType(fieldClass)) {
                // Conventional base type
                handleBasicType(field, object, fieldName, result, recursion);
            } else {
                // Object type, recursive get
                Map<String, Object> childResultMap = getKeyValueByObject(getFieldValueByObject(field, object), true);
                if (!childResultMap.isEmpty()) {
                    for (Map.Entry<String, Object> entry : childResultMap.entrySet()) {
                        result.put(fieldName + entry.getKey(), entry.getValue());
                    }
                }
            }
        }
        return result;
    }

    /**
     * Handling collections, key-values and array types
     *
     * @param field       Attribute Field Objects
     * @param target      target object
     * @param clazz       Class object corresponding to the attribute
     * @param fieldObject The property itself object
     * @return result
     */
    private static Map<String, Object> handleCollectMapArray(Field field, Object target, Class<?> clazz, Object fieldObject) {
        if (fieldObject == null) {
            return Collections.EMPTY_MAP;
        }
        Map<String, Object> result = new HashMap<>(8);
        if (clazz.isArray()) {
            // Current array component type
            Class<?> componentType = clazz.getComponentType();
            Object arrayValue = getFieldValueByObject(field, target);
            if (arrayValue == null) {
                return Collections.EMPTY_MAP;
            }
            int arrayLen = Array.getLength(arrayValue);
            for (int i = 0; i < arrayLen; i++) {
                if (isBasicGenericType(componentType)) {
                    // 如果是基础类型
                    result.put("[" + i + "]", Array.get(arrayValue, i));
                } else {
                    Map<String, Object> objectMap = getKeyValueByObject(Array.get(arrayValue, i), true);
                    for (Map.Entry<String, Object> entry : objectMap.entrySet()) {
                        result.put("[" + i + "]" + entry.getKey(), entry.getValue());
                    }
                }
            }
        } else if (fieldObject instanceof Collection) {
            // if the type is collection
            Collection collection = (Collection) fieldObject;
            Iterator iterator = collection.iterator();
            int index = 0;
            while (iterator.hasNext()) {
                Object nextObject = iterator.next();
                if (isBasicGenericType(nextObject.getClass())) {
                    result.put("[" + index + "]", nextObject);
                } else {
                    Map<String, Object> objectMap = getKeyValueByObject(nextObject, true);
                    for (Map.Entry<String, Object> entry : objectMap.entrySet()) {
                        result.put("[" + index + "]" + entry.getKey(), entry.getValue());
                    }
                }

                index++;
            }
        } else if (fieldObject instanceof Map) {
            // if the type is Map
            Map<?, ?> map = (Map) fieldObject;
            for (Map.Entry<?, ?> entry : map.entrySet()) {
                String key = String.valueOf(entry.getKey());
                if (isBasicGenericType(entry.getValue().getClass())) {
                    result.put("." + key, entry.getValue());
                } else {
                    Map<String, Object> objectMap = getKeyValueByObject(entry.getValue(), true);
                    for (Map.Entry<String, Object> objectEntry : objectMap.entrySet()) {
                        result.put("." + key + objectEntry.getKey(), objectEntry.getValue());
                    }
                }
            }
        } else {
            logger.warn("not found class type!");
        }

        return result;
    }

    /**
     * Handling of conventional foundation types
     *
     * @param field
     * @param target
     * @param fieldName
     * @param result
     */
    private static void handleBasicType(Field field, Object target, String fieldName, Map<String, Object> result, boolean recursion) {
        Assert.notNull(result, "result collect is null!");
        Object value = getFieldValueByObject(field, target);
        if (recursion) {
            // 递归获取数据是需要带上.前缀
            result.put("." + fieldName, value);
        } else {
            result.put(fieldName, value);
        }
    }

    /**
     * Determining whether a type is basic or not
     * like String,int,long ... Date
     *
     * @param clazz
     * @return
     */
    private static boolean isBasicGenericType(Class<?> clazz) {
        // String类型
        return (String.class.isAssignableFrom(clazz) ||
                // 八大基本数据类型包装类，以及BigDecimal,AtomicInteger等数据类型
                Number.class.isAssignableFrom(clazz) || Boolean.class.isAssignableFrom(clazz) || Character.class.isAssignableFrom(clazz) ||
                // 时间类型
                Date.class.isAssignableFrom(clazz) || java.security.Timestamp.class.isAssignableFrom(clazz) ||
                // 八大基本数据类型
                int.class.isAssignableFrom(clazz) || double.class.isAssignableFrom(clazz) ||
                float.class.isAssignableFrom(clazz) || short.class.isAssignableFrom(clazz) ||
                boolean.class.isAssignableFrom(clazz) || char.class.isAssignableFrom(clazz) ||
                long.class.isAssignableFrom(clazz) || byte.class.isAssignableFrom(clazz));
    }

    /**
     * Get the set of all property names of a Class object
     *
     * @param clazz
     * @return
     */
    public static List<String> getFieldsName(Class<?> clazz) {
        if (clazz.getSuperclass() == null) {
            // 说明为Object.class
            return Collections.EMPTY_LIST;
        }
        List<String> fieldList = new LinkedList<>();
        // 拿到当前所有 属性值，包括私有和保护 默认
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            fieldList.add(field.getName());
        }
        // 父类为Object.class说明 没有父类，有判断是否为Object.class是有问题的，所以多次判断父类父类是否为空
        if (clazz.getSuperclass() != Object.class && clazz.getSuperclass().getSuperclass() != null) {
            // 说明有父类
            List<String> fieldsName = getFieldsName(clazz.getSuperclass());
            fieldList.addAll(fieldsName);
        }

        return fieldList;
    }

    /**
     * Gets the class type of the specified attribute name, without a generic.
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    public static Class<?> getFieldClass(Class<?> clazz, String fieldName) {
        // 到 Object 都没有找到
        if (clazz.isAssignableFrom(Object.class) || clazz.getSuperclass() == null) {
            return null;
        }
        try {
            Field declaredField = clazz.getDeclaredField(fieldName);
            return declaredField == null ? null : declaredField.getType();
        } catch (Throwable e) {
            if (NoSuchFieldException.class.isAssignableFrom(e.getClass())) {
                // 说明当前类未找到，在从父类里面找
                return getFieldClass(clazz.getSuperclass(), fieldName);
            } else {
                // 其他异常
                logger.error("get class field error", e);
                return null;
            }
        }
    }

    /**
     * Get the Field object with the specified property name.
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    public static Field getField(Class<?> clazz, String fieldName) {
        // 到 Object 都没有找到
        if (clazz.isAssignableFrom(Object.class) || clazz.getSuperclass() == null) {
            return null;
        }
        try {
            return clazz.getDeclaredField(fieldName);
        } catch (Throwable e) {
            if (NoSuchFieldException.class.isAssignableFrom(e.getClass())) {
                // 说明当前类未找到，在从父类里面找
                return getField(clazz.getSuperclass(), fieldName);
            } else {
                // 其他异常
                logger.error("get class field", e);
                return null;
            }
        }
    }

    /**
     * Determines if the specified class is a collection or a Map or an Array.
     *
     * @param clazz
     * @return
     */
    private static boolean isCollectOrMapOrArrayClass(Class<?> clazz) {
        return (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz) || clazz.isArray());
    }

    /**
     * Get the type array of the object array
     *
     * @param args
     * @return
     */
    public static Class[] getParameterObjectClass(Object[] args) {
        if (args == null) return null;
        Class[] classes = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            classes[i] = args[i].getClass();
        }
        return classes;
    }

}

